package de.sordul;

import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.List;

import javax.smartcardio.*;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import de.sordul.model.CardCharacteristics;
import de.sordul.model.CardVersion;
import de.sordul.model.CoreDataStatus;
import de.sordul.model.ICCSN;
import de.sordul.model.InsuranceData;
import de.sordul.model.PatientData;
import de.sordul.model.RawData;

/**
 * Base class exposing all methods of LibEGK. The first found terminal is used if none is selected explicitly.
 */
public class LibEGK {
	public static final String EGK_ROOT 						= "00 A4 04 0C 06 D2 76 00 01 44 80 00";
	public static final String EGK_HCA 							= "00 A4 04 0C 06 D2 76 00 00 01 02";
	public static final String EGK_ROOT_CHARACTERISTICS 		= "00 B0 00 02 00";
	public static final String EGK_ROOT_CHARACTERISTICS_LENGTH	= "00 B0 9D 00 02";
	public static final String EGK_ROOT_VERSION 				= "00 B0 00 02 00";
	public static final String EGK_ROOT_VERSION_LENGTH			= "00 B0 90 00 02";
	public static final String EGK_ROOT_ICCSN 					= "00 B0 00 02 00";
	public static final String EGK_ROOT_ICCSN_LENGTH			= "00 B0 81 00 02";
	public static final String EGK_HCA_CDATASTATUS 				= "00 B0 00 02 00";
	public static final String EGK_HCA_CDATASTATUS_LENGTH		= "00 B0 8C 00 02";
	public static final String EGK_HCA_INSURANCEDATA 			= "00 B0 82 00 00 00 00";
	public static final String EGK_HCA_INSURANCEDATA_LENGTH		= "00 B0 82 00 08";
	public static final String EGK_HCA_PATIENTDATA 				= "00 B0 00 02 00";
	public static final String EGK_HCA_PATIENTDATA_LENGTH		= "00 B0 81 00 02";
	public static final String EGK_STATUS_SUCCESS				= "90 00";
	
	private static LibEGK instance = null;
    TerminalFactory factory;
    List<CardTerminal> terminals;
    CardTerminal terminal;
    Card card;
    CardChannel channel;
	
	/**
	 * Getter method for the singleton.
	 * @return LibEGK instance
	 * @throws CardException 
	 * @throws LibEGKException 
	 */
	public static LibEGK getInstance() throws CardException, LibEGKException {
		if(instance == null) {
			instance = new LibEGK();
		}
		
		return instance;
	}
		
	/**
	 * Make default constructor private since this class should be a singleton.
	 * @throws CardException 
	 * @throws LibEGKException 
	 */
	private LibEGK() throws CardException, LibEGKException {
		factory = TerminalFactory.getDefault();
		terminals = factory.terminals().list();
		
		if(terminals.size() == 0) {
			throw new LibEGKException("No card terminals present!");
		}
		terminal = terminals.get(0);
	}
	
	/**
	 * Sets the card terminal to use for chipcard access.
	 * @param name Name of the terminal to use
	 * @return TRUE for succesfull set, FALSE otherwise
	 */
	public boolean setCardTerminal(String name) {
		
		for(CardTerminal term: terminals) {
			if(term.getName().equals(name)) {
				terminal = term;
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Checks whenever a chipcard is inserted into the card reader or not.
	 * @return TRUE for yes, FALSE otherwise
	 * @throws CardException 
	 */
	public Boolean isCardInserted() throws CardException {
		return terminal.isCardPresent();
	}
	
	/**
	 * Establishes a connection to the card.
	 * @throws CardException 
	 * @return TRUE if connection has been set up, FALSE otherwise
	 */
	public boolean connectCard() throws CardException {
		card = terminal.connect("T=1");
		
		if(card != null) {
			return true;
		}
		
		return false;
	}
	
	/**
	 * Disconnects the card from the reader.
	 * @throws CardException 
	 * @return TRUE if card has been disconnected, FALSE otherwise (currently just the exception)
	 */
	public boolean disconnectCard() throws CardException {
		if(card != null) {
			card.disconnect(false);
			card = null;
		}
		
		return true;
	}
	
	/**
	 * Returns the card characteristics.
	 * @return CardCharacteristics
	 */
	public CardCharacteristics getCardCharacteristics() {
		return null;
	}
	
	/**
	 * Returns the cards ICCSN number.
	 * @return ICCSN
	 */
	public ICCSN getICCSN() {
		return null;
	}
	
	/**
	 * Returns the cards version information.
	 * @return CardVersion
	 */
	public CardVersion getCardVersion() {
		return null;
	}
	
	/**
	 * Returns the cards core data status.
	 * @return CoreDataStatus
	 */
	public CoreDataStatus getCoreDataStatus() {
		return null;
	}
	
	/**
	 * Returns the card owners personal data.
	 * @return PatientData
	 * @throws CardException 
	 * @throws SAXException 
	 * @throws ParserConfigurationException 
	 * @throws IOException 
	 * @throws ParseException 
	 */
	public PatientData getPatientData() throws CardException, IOException, ParserConfigurationException, SAXException, ParseException {
		byte[] apduGetPersonalDataLength 	= SCUtils.stringToBytes(this.EGK_HCA_PATIENTDATA_LENGTH);
		byte[] apduGetPersonalData 			= SCUtils.stringToBytes(this.EGK_HCA_PATIENTDATA);
		byte[] apduSelectHCAApplication		= SCUtils.stringToBytes(this.EGK_HCA);
        RawData entry;
        PatientData pData;
		
        // Select application
        CardChannel channel = card.getBasicChannel();
        ResponseAPDU rSelectApplication = channel.transmit(new CommandAPDU(apduSelectHCAApplication));
        System.out.println("\nResponse to 'Select application':\n" + SCUtils.bytesToString(rSelectApplication.getBytes()) );
        
        // Get personal data length
        ResponseAPDU rGetPersonalDataLength = channel.transmit(new CommandAPDU(apduGetPersonalDataLength));
        System.out.println("\nResponse to 'Get personal data length':\n" + SCUtils.bytesToString(rGetPersonalDataLength.getBytes()) );
        
        // Get personal data
        byte[] personalDataLength = { rGetPersonalDataLength.getBytes()[0], rGetPersonalDataLength.getBytes()[1] };
        apduGetPersonalData = SCUtils.appendBytes(apduGetPersonalData, personalDataLength);
        ResponseAPDU rGetPersonalData = channel.transmit(new CommandAPDU(apduGetPersonalData));
        System.out.println("\nResponse to 'Get personal data':\n" + SCUtils.bytesToString(rGetPersonalData.getBytes()) );
        
        entry = new RawData(rGetPersonalData.getBytes());
        pData = new PatientData(entry);
		
		return pData;		
	}
	
	/**
	 * Returns the card owners insurance data.
	 * @return InsuranceData
	 * @throws CardException 
	 * @throws SAXException 
	 * @throws ParserConfigurationException 
	 * @throws IOException 
	 */
	public InsuranceData getInsuranceData() throws CardException, IOException, ParserConfigurationException, SAXException {
		byte[] apduGetInsuranceData 		= SCUtils.stringToBytes(this.EGK_HCA_INSURANCEDATA);
		byte[] apduSelectHCAApplication		= SCUtils.stringToBytes(this.EGK_HCA);
		byte[] truncatedResponse			= null;
        RawData entry;
        InsuranceData iData = null;
		
        // Select application
        CardChannel channel = card.getBasicChannel();
        ResponseAPDU rSelectApplication = channel.transmit(new CommandAPDU(apduSelectHCAApplication));
        System.out.println("\nResponse to 'Select application':\n" + SCUtils.bytesToString(rSelectApplication.getBytes()) );
        
        // Get insurance data
    	ResponseAPDU rGetPersonalData = channel.transmit(new CommandAPDU(apduGetInsuranceData));
        System.out.println("\nResponse to 'Get insurance data':\n" + SCUtils.bytesToString(rGetPersonalData.getBytes()) );        
        truncatedResponse = Arrays.copyOfRange(rGetPersonalData.getBytes(), 8, rGetPersonalData.getBytes().length);
        
        entry = new RawData(truncatedResponse);
        iData = new InsuranceData(entry);

		
		return iData;	
	}
}
